/**
A multi shaped mine sweeper game
SDLHexamine  Copyright (C) 2009-2010  Andematunum-Software
(andematunum-software@googlegroups.com)

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _board_h_
#define _board_h_

#include "SDL.h"
#include <iostream>
#include <vector>
using namespace std;

#define DEBUG_MineBoard

/**
  * The cells have 2 kinds of organization:
  * An internal one keeping the cell connectivity and
  * states (can be a mesh or an array).
  * A logical one represented by a bidimensional grid,
  * and corresponding to the graphical layer (where the
  * player has clicked).
  */

/**
  * Possible states of a cell in the
  * cell matrix.
  */
enum CellState {
  CELL_NONE,    //< No cell at the given position.
                //  Only used in the case an *array* representation
                //  is chosen.
  CELL_EMPTY,   //< Nothing under the cell
  CELL_MINE     //< A mine under the cell
};

/**
  * Position of a cell in the grid (logical layer).
  * Signed integers are used here because some
  * conventions may need them.
  */
struct CellGridId {
  Sint16 row;
  Sint16 col;

  CellGridId (Sint16 row, Sint16 col) {
    this->row = row;
    this->col = col;
  }
};

/**
  * Position of a cell in the data array (internal layer).
  */
struct CellArrayId {
  Uint16 row;
  Uint16 col;

  CellArrayId(Uint16 row, Uint16 col) {
    this->row = row;
    this->col = col;
  }
  Uint16 to_indice (Uint16 width) {
    return row*width+col;
  }
};

/**
  * Various possible shapes for the mine board.
  */
enum MineBoardShape {
  SHAPE_TRIANGULAR,
  SHAPE_RECTANGULAR,
  SHAPE_HEXAGONAL,
  SHAPE_CIRCULAR
};

/**
  * Initialization options for the mine board.
  */
struct MineBoardParameters {
  MineBoardShape shape; //< Shape of the board
  Uint16 n_mines;       //< Number of mines to place
  Uint16 sizes[3];      //< Integers used in the
                        //  initialization, like the number
                        //  of rings, the width or height, etc.
                        //  Some may be unused.
  
  MineBoardParameters () {
  }
  MineBoardParameters (
      MineBoardShape shape,
      Uint16 n_mines,
      Uint16 sizes[3]) {
    this->shape = shape;
    this->n_mines = n_mines;
    for (int k = 0; k < 3; k++) {
      this->sizes[k] = sizes[k];
    }
  }
};

/**
  * Top-level, virtual class for mine boards
  * with various geometries (triangles, squares,
  * hexagons, etc).
  * The templated factory frees the subclasses from
  * duplicating the construction code so that they
  * can focus on implementing the virtual
  * methods only.
  *
  * Only one board is allowed at a time (singleton).
  * It must be created at the beginning and
  * destroyed at the end of each game.
  */
class MineBoard {
  public:
    /**
      * Entry point for the external client code,
      * to build a new mine board.
      * The template parameter gives the type of board
      * to create.
      * \params params Board parameters
      * \return The unique instance of one of the subclasses.
      *
      * \sa MineBoardParameters
      */
    template<class T>
    static MineBoard* create (MineBoardParameters& params) {
      if (instance != NULL) {
        throw "Only one instance of MineBoard can exist at a time.";
      }
      T* board = new T;
      if (board == NULL) {
        throw "Cannot allocated memory for a new MineBoard.";
      }
#ifdef DEBUG_MineBoard
      cout << "*** DEBUG: Created MineBoard: " << board << endl;
#endif
      board->set_parameters(params);
      board->initialize();
      instance = board;
      return board;
    }

    /**
      * Destroy the unique instance.
      */
    static void destroy () {
      delete instance;
#ifdef DEBUG_MineBoard
      cout << "*** DEBUG: Deleted MineBoard: " << instance << endl;
#endif
      instance = NULL;
    }

    /**
      * Initialize the board according to the parameters.
      * This is dependent on the shape of the cells.
      * It includes:
      *   Memory initialization for the cells:
      *   - Use: MineBoard::allocate_cells(width,height);
      *   Random placement of the mines
      *   - Use: MineBoard::set_state(CellArrayId,CellState);
      *          CellState MineBoard::get_state(CellArrayId);
      */
    virtual void initialize () = 0;

    /**
      * \param id Cell to query
      * \return the state of \p cell
      * \sa CellGridId
      * \sa CellState
      */
    virtual CellState get_state (CellGridId cell) {
      return get_state(grid2array(cell));
    }

    /**
      * \param id Cell to query
      * \return The number of mines around \p cell
      * \sa CellGridId
      * \sa CellState
      */
    virtual int get_number_of_mines (CellGridId cell) = 0;

    /**
      * \param cell Cell to query
      * \param neighbors List of the identifiers (i,j) of the neighbors
      *                  of \p cell
      */
    virtual void get_neighbors (CellGridId cell,
                                vector<CellGridId>& neighbors) = 0;

    /**
      * Convert a cell position in the logical coordinate space into
      * a cell position in the data array coordinate space.
      * \param cell_in_grid position of the cell in the logical space
      * \return the position of the cell in the data array space
      */
    virtual CellArrayId grid2array (CellGridId cell_in_grid) = 0;

    /**
      * Convert a cell position in the data array coordinate space into
      * a cell position in the logical coordinate space.
      * \param cell_in_array position of the cell in the data array space
      * \return the position of the cell in the logical space
      */
    virtual CellGridId array2grid (CellArrayId cell_in_array) = 0;
    
    /**
      * Get the unique instance.
      */
    static MineBoard* get_instance () {
      if (instance == NULL) {
        throw "There is no MineBoard initialized !";
      }
      return instance;
    }

    // Pour test !
    virtual void say_hello () = 0;

  protected:
    MineBoard ()  {
    }
    ~MineBoard () {
      free_cells();
    }
    void set_parameters (MineBoardParameters& params) {
      this->params = params;
    }
 
    /**
      * Allocate the memory for the data array.
      * \param width Number of cells in width
      * \param height Number of cells in height
      */
    void allocate_cells (Uint16 width, Uint16 height);

    /**
      * Free the memory occupied by the data array (cells).
      */
    void free_cells () {
      delete [] cells;
      cells = NULL;
#ifdef DEBUG_MineBoard
      cout << "*** DEBUG: Freed cells." << endl;
#endif
    }

    /**
      * Change the state of a cell in the data array.
      * \param cell (row,col) position of the cell in the data array
      * \param state state of \cell to change
      */
    void set_state (CellArrayId cell, CellState state) {
      cells[cell.to_indice(width)] = state;
    }

    /**
      * Read the state of a cell from the data array.
      * \param cell (row,col) position of the cell in the data array
      * \return the state of \cell
      */
    CellState get_state (CellArrayId cell) {
      return cells[cell.to_indice(width)];
    }

  protected:
    // Singleton.
    static MineBoard* instance;
    // Parameters of the board (shape, size, etc.)
    MineBoardParameters params;
    // Board data.
    // TODO: possible improvement for supporting
    // mesh boards or array boards through abstraction classes
    // with accessors.
    CellState* cells;
    // Width of the board, used for converting 2d coordinates
    // into 1d coordinates in the board data.
    Uint16 width;
};

/**
  * Mine board based on hexagonal cells.
  */
class HexagonalMineBoard: public MineBoard {
  /*
     Gautier, c'est ici que tu entres en piste, en donnant une implémentation
     aux fonctions virtuelles pures de MineBoard.
     La 1ère chose à faire est de lire attentivement les commentaires que j'ai
     mis à chacune, tout particulièrement à 'initialize' qui est la plus
     critique de toutes (tu as un petit exemple d'implémentation ici).

     J'ai écrit tout le code 'dangereux' (initialisation, libération mémoire),
     tu peux te concentrer sur l'aspect purement métier, en utilisant les
     accesseurs fournis.

     Parmis les fonctions à implémenter il y a les 2 fonctions de conversion
     pour passer d'un système de coordonnées à un autre (couches logique et
     interne).

     Tu n'as pas à te préoccuper de la partie chiante du singleton, etc, c'est
     fait. A présent pour créer un tableau de jeu hexagonal on fera:

     MineBoard* board = MineBoard::create<HexagonalMineBoard> (parameters);
     ( BLABLABLA )
     MineBoard::destroy();

     (Exemple dans le main.cpp)

     En espérant que cela soit assez clair ... ;)
     ça évite de répéter les constructeurs avec paramètres dans chaque sous
     classe ! Magic Template ...
  */

  public:
  // Juste pour le test !
  void say_hello () {
    cout << "Hello, I'm an hexagonal mine board !" << endl;
  }

  virtual void initialize () {
    // EXEMPLE
    // Allouer un tableau de 32x16 (les tailles nécessitent
    // un calcul dont toi seul a le secret :))
    allocate_cells(32,16);
    // Changer l'état d'une cellule, ici placer une mine à (0,0)
    set_state(CellArrayId(0,0), CELL_MINE);
  }

  /**
   * \param id Cell to query
   * \return The number of mines around \p cell
   * \sa CellGridId
   * \sa CellState
   */
  virtual int get_number_of_mines (CellGridId cell) {
    return 0;
  }

  /**
   * \param cell Cell to query
   * \param neighbors List of the identifiers (i,j) of the neighbors
   *                  of \p cell
   */
  virtual void get_neighbors (CellGridId cell,
                              vector<CellGridId>& neighbors) {
  }

  /**
   * Convert a cell position in the logical coordinate space into
   * a cell position in the data array coordinate space.
   * \param cell_in_grid position of the cell in the logical space
   * \return the position of the cell in the data array space
   */
  virtual CellArrayId grid2array (CellGridId cell_in_grid) {
    return CellArrayId(0,0);
  }

  /**
   * Convert a cell position in the data array coordinate space into
   * a cell position in the logical coordinate space.
   * \param cell_in_array position of the cell in the data array space
   * \return the position of the cell in the logical space
   */
  virtual CellGridId array2grid (CellArrayId cell_in_array) {
    return CellGridId(0,0);
  }

};

#endif

